Skip to content

feat: switch all cross-chain address fields to bytes32#108

Merged
JoE11-y merged 4 commits intomainfrom
stellar-integrations
Apr 12, 2026
Merged

feat: switch all cross-chain address fields to bytes32#108
JoE11-y merged 4 commits intomainfrom
stellar-integrations

Conversation

@JoE11-y
Copy link
Copy Markdown
Contributor

@JoE11-y JoE11-y commented Apr 12, 2026

EVM: switch all cross-chain address fields to bytes32

Why

Stellar contract IDs and account IDs are 32 bytes wide. EVM address is 20. Until now the EVM contracts stored cross-chain identifiers as address, which forced Stellar's 32-byte IDs to be truncated to their low 20 bytes whenever they round-tripped through EVM. That truncation made it impossible to recover the original Stellar ID from anything an EVM contract had seen, and it forced the EIP-712 order hash to diverge between chains — the EVM side was hashing truncated values while the Stellar side was hashing the full 32 bytes.

This PR widens every cross-chain identifier on the EVM side to bytes32 so the same value round-trips losslessly in either direction and both chains compute byte-identical EIP-712 hashes.

What changed

ORDER_TYPEHASH

All address-typed members of Order are now bytes32. The type string is:

Order(bytes32 orderChainToken,bytes32 adChainToken,uint256 amount,bytes32 bridger,uint256 orderChainId,bytes32 orderPortal,bytes32 orderRecipient,uint256 adChainId,bytes32 adManager,string adId,bytes32 adCreator,bytes32 adRecipient,uint256 salt)

New typehash: 0x1ca8761ed9ca27de2766ec2a5767edf8231ab90bcd988058bc64b2966494fe9b. The same constant is now used on both EVM and Stellar (contracts/stellar/lib/proofbridge-core/src/eip712.rs).

AdManager.sol and OrderPortal.sol

  • OrderParams: every cross-chain address field (orderChainToken, bridger, orderPortal / srcOrderPortal, orderRecipient, adCreator, adRecipient, adManager, adChainToken) is now bytes32.
  • ChainInfo.orderPortal / ChainInfo.adManagerbytes32.
  • Ad.adRecipientbytes32 (EVM contracts still use address maker for the ad creator's local identity, which is checked against params.adCreator via toBytes32).
  • tokenRoute: mapping(address => mapping(uint256 => bytes32)) — the remote token is now stored as full 32 bytes.
  • setChain(uint256, bytes32, bool) and setTokenRoute(address, bytes32, uint256) / setTokenRoute(address, uint256, bytes32) take bytes32 for the remote side.
  • Events (ChainSet, TokenRouteSet, TokenRouteRemoved, OrderLocked, OrderUnlocked) emit bytes32 for remote / cross-chain user fields.
  • createAd(..., bytes32 adRecipient) and createAdRequestHash(..., bytes32 adRecipient, ...).

Local-address extraction

Some fields inside OrderParams refer to local EVM participants (e.g. orderRecipient on the order-chain side, adCreator / adRecipient on the ad-chain side). For those we still need a 20-byte address at call time — to transfer ERC-20s, to check msg.sender, etc.

New helpers:

  • toBytes32(address) internal pure returns (bytes32) — left-pads with zeros.
  • toAddressChecked(bytes32) internal pure returns (address) — extracts the low 20 bytes and reverts with AdManager__NotEvmAddress(bytes32) / OrderPortal__NotEvmAddress(bytes32) if the upper 12 bytes are non-zero. This guarantees we never silently truncate a Stellar-shaped ID into a (wrong) EVM address.

unlock on both contracts uses toAddressChecked on the local recipient before transferring tokens.

Validation changes in validateOrder

Where the contract previously compared address to address, it now compares bytes32 to bytes32 directly. Ad-side identity checks become, e.g.:

  • params.adCreator != toBytes32(ad.maker) — local maker is widened to bytes32 for comparison.
  • params.adChainToken != toBytes32(ad.token) — same.
  • params.adRecipient != ad.adRecipient — both are already bytes32.

Solidity tests

All three test suites (AdManager.t.sol, OrderPortal.t.sol, ProofBridge.t.sol) updated:

  • _b32(address) helper for building bytes32 values.
  • OrderParams literals updated to use bytes32 fields throughout.
  • Return-tuple destructuring updated for chains(...), ads(...), tokenRoute(...).
  • Event signature matchers and custom-error selectors updated.
  • balanceOf lookups cast a stored bytes32 back to address via address(uint160(uint256(b32))) where needed.

All 84 Foundry tests pass, including the cross-chain EIP-712 parity tests that hash the same OrderParams on both sides and assert bytewise equality.

Off-chain / scripts

  • contracts/evm/js-scripts/utils.ts — ethers orderTypes uses bytes32 for every address-typed field so signTypedData produces the same struct hash the contract computes.
  • contracts/evm/js-scripts/deploy/core/utils.ts — updated AD_MANAGER_ABI / ORDER_PORTAL_ABI fragments (setChain, setTokenRoute, chains, tokenRoute return types).
  • contracts/evm/js-scripts/deploy/core/setup.tsaddrToBytes32 / b32EqAddr helpers; every setChain / setTokenRoute call now passes a left-padded bytes32 for the remote counterpart.
  • scripts/cross-chain-e2e/run.ts — passes full-width 32-byte values for adManager, adToken, adCreator, adRecipient, bridger, orderRecipient, orderPortal. stellarIdToEvmAddress is gone — no more truncation.
  • scripts/cross-chain-e2e/lib/proof.ts and contracts/stellar/tests/fixtures/generate_fixtures.tsORDER_TYPEHASH string updated to the unified bytes32 form.

Stellar side

Mostly no-ops, because Stellar already used BytesN<32> for every cross-chain field. Two concrete updates:

  • proofbridge-core/src/eip712.rs: new ORDER_TYPEHASH constant matching the new keccak.
  • proofbridge-core/src/test.rs: test_order_typehash asserts the new type string.
  • Integration-test fixtures regenerated: new order hash 0x06e3db29ab3f869edb083ac922864cdaed9cf70a0d8e2205881a2ce5f49cbbde.

Test status

  • EVM Foundry: 84/84 passing, including cross-chain hash parity tests.
  • Stellar proofbridge-core: 15/15 unit tests passing (including test_order_typehash).
  • Stellar integration test: 5/5 passing end-to-end on the updated fixtures (lock_for_order, unlock_with_bridger_proof, full_cross_chain_flow, nullifier_prevents_double_unlock, order_portal_create_and_unlock).

Migration notes

This is a breaking ABI change. Any off-chain client (relayer, indexer, frontend) that encodes OrderParams, reads chains(chainId) / tokenRoute(...), or subscribes to ChainSet / TokenRouteSet / OrderLocked / OrderUnlocked must:

  1. Re-generate TypeChain / ABIs from the rebuilt artifacts.
  2. Switch EIP-712 type definitions for Order to the new bytes32 shape.
  3. Left-pad any EVM-address-shaped field (bridger, orderRecipient, adCreator, adRecipient, orderPortal, adManager, …) with 12 zero bytes before passing it into a contract call. On the way out, fields whose value is a local EVM address can be recovered via address(uint160(uint256(b32))).

Summary by CodeRabbit

  • New Features

    • End-to-end cross-chain test harness with automated orchestration and ZK-proof generation for order flow.
    • Node scripts and utilities to deploy, wire, and exercise Stellar↔EVM scenarios.
  • Refactor

    • Unified cross-chain identifier representation across chains and tooling for consistent routing and order handling.
    • Simplified Stellar admin configuration: admin calls now use native authentication.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6778d1a1-98b0-4c51-944b-e79e1efed567

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR migrates many cross-chain "address-like" fields to bytes32 across EVM contracts, JS/ABI helpers, tests, and EIP‑712 hashes; simplifies Stellar ad-manager admin auth to require_auth(); and adds a new TypeScript + Bash end-to-end cross-chain test framework (Stellar ↔ EVM) with ZK proof generation.

Changes

Cohort / File(s) Summary
Core Contracts (EVM)
contracts/evm/src/AdManager.sol, contracts/evm/src/OrderPortal.sol
Replaced multiple address fields with bytes32 in structs, events, errors, mappings, EIP‑712 typehash/struct hashing, and public APIs (setChain, setTokenRoute, createAd, unlock). Added toAddressChecked(bytes32) and corresponding NotEvmAddress errors.
ABIs & EIP‑712 Types (JS / Rust / Fixtures)
contracts/evm/js-scripts/deploy/core/utils.ts, contracts/evm/js-scripts/utils.ts, contracts/stellar/lib/proofbridge-core/src/eip712.rs, contracts/stellar/lib/proofbridge-core/src/test.rs, contracts/stellar/tests/fixtures/generate_fixtures.ts
Updated ABI fragments and EIP‑712 ORDER_TYPEHASH/type strings to use bytes32 for token/portal/manager/recipient fields; adjusted JS typed-data schemas accordingly.
JS Deploy Helpers
contracts/evm/js-scripts/deploy/core/setup.ts
Added addrToBytes32() and b32EqAddr() helpers; updated chain linking and token-route configuration to use bytes32 conversions and equality checks.
EVM Tests
contracts/evm/test/Admanager.t.sol, contracts/evm/test/OrderPortal.t.sol, contracts/evm/test/ProofBridge.t.sol
Introduced _b32() and conversion helpers; updated tests to construct/expect bytes32 identifiers and convert back to addresses where needed for balance checks and assertions.
Stellar AdManager (Rust)
contracts/stellar/contracts/ad-manager/src/lib.rs
Removed EIP‑712/auth params from admin entrypoints (signature, public_key, auth_token, time_to_expire); now use config.admin.require_auth() only. Removed request-hash replay tracking; added zero-address validation for set_chain.
Stellar Tests / Integration
contracts/stellar/tests/integration_test.rs
Simplified test calls to updated ad-manager admin API (removed signed request construction) for chain and token-route setup; updated fixtures accordingly.
Cross-chain E2E framework (new)
scripts/cross-chain-e2e/lib/evm.ts, .../lib/stellar.ts, .../lib/proof.ts, .../lib/signing.ts
Added EVM deployment helpers, Stellar CLI wrappers and strkey/hex conversions, ZK proof generation (Noir + Barretenberg) including order hashing and public-input builders, and dual signing utilities (Ed25519 and EVM request signing).
E2E Runner & Tooling (new)
scripts/cross-chain-e2e/run.ts, scripts/run_cross_chain_e2e.sh, scripts/cross-chain-e2e/package.json, scripts/cross-chain-e2e/tsconfig.json, pnpm-workspace.yaml
Added a TypeScript runner that orchestrates deploy/link/create/lock/prove/unlock flows across Stellar and EVM, a Bash orchestration script (Stellar localnet + Anvil + build steps), package manifest, TS config, and added workspace package.
CI Workflow (new, commented)
.github/workflows/cross-chain-e2e.yml
Added a GitHub Actions workflow definition for cross-chain E2E (toolchain installs and script execution), but triggers/steps are commented out so it does not run by default.

Sequence Diagram(s)

sequenceDiagram
    participant Runner as E2E Runner
    participant Stellar as Stellar Localnet
    participant EVM as EVM (Anvil)
    participant ZK as ZK Proof System
    participant AdMgr as Stellar AdManager
    participant OrderP as EVM OrderPortal

    Runner->>Stellar: deploy verifier, merkleManager, adManager
    Runner->>EVM: deploy verifier, merkleManager, orderPortal
    Runner->>Stellar: set_chain / set_token_route (signed)
    Runner->>EVM: orderPortal.setChain / setTokenRoute
    Runner->>Stellar: create_ad (signed)
    Runner->>EVM: createOrder (mint/approve + signed request)
    Runner->>Stellar: lock_for_order (signed)
    Runner->>ZK: generateProofs(order, circuit)
    ZK-->>Runner: proofs, nullifiers, targetRoot
    Runner->>Stellar: unlock (signed + proof)
    Stellar->>AdMgr: validate proof, transfer tokens
    Runner->>EVM: unlock (signed + proof)
    EVM->>OrderP: validate proof, transfer tokens -> order Filled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Suggested reviewers

  • Ugo-X

Poem

🐰 I packed addresses into bytes32 tight,

Hopped through EVM, Stellar—what a sight!
Proofs were woven, bridges stitched with care,
Tests now run end‑to‑end across the air,
Hop hop—cross‑chain magic in the code tonight. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically summarizes the main change: switching all cross-chain address fields from the 20-byte address type to the 32-byte bytes32 type.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stellar-integrations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
contracts/stellar/contracts/ad-manager/src/lib.rs (2)

91-109: ⚠️ Potential issue | 🟡 Minor

Fix formatting to pass CI.

The pipeline failure indicates cargo fmt check failed on the set_manager function signature. Run cargo fmt --all to apply the expected formatting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/stellar/contracts/ad-manager/src/lib.rs` around lines 91 - 109, The
function signature for set_manager (pub fn set_manager(env: Env, manager:
Address, status: bool) -> Result<(), AdManagerError>) is not formatted per
rustfmt; run cargo fmt --all (or apply rustfmt) to reformat the declaration and
body so CI passes, ensuring the signature and parameter alignment for
set_manager, and keep surrounding symbols (Env, Address, AdManagerError,
storage::set_manager, events::ManagerUpdated) intact.

193-216: ⚠️ Potential issue | 🟠 Major

Update e2e scripts to use the new admin API without signature/auth_token parameters.

The set_chain and set_token_route calls in scripts/cross-chain-e2e/run.ts (lines 201-209 and 224-232) still pass --signature, --public_key, --auth_token, and --time_to_expire parameters. These functions now use Soroban's native require_auth() and no longer accept these parameters. Remove these parameters and adjust the calls to match the new signatures: set_chain(order_chain_id, order_portal, supported) and set_token_route(ad_token, order_token, order_chain_id).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/stellar/contracts/ad-manager/src/lib.rs` around lines 193 - 216,
E2E script calls still pass legacy auth flags to the admin functions; update the
script invocations that call set_chain and set_token_route to remove the
obsolete CLI flags (--signature, --public_key, --auth_token, --time_to_expire)
and invoke the new signatures instead: call set_chain(order_chain_id,
order_portal, supported) and call set_token_route(ad_token, order_token,
order_chain_id); ensure no manual signature/auth_token handling remains and rely
on the contract's require_auth() enforcement (referenced in the contract
functions requiring admin auth).
contracts/evm/src/AdManager.sol (1)

1154-1188: ⚠️ Potential issue | 🔴 Critical

Reject non-EVM orderRecipient values during lockForOrder().

unlock() assumes params.orderRecipient is a local EVM address and converts it with toAddressChecked(), but validateOrder() only checks for non-zero. That means liquidity can be locked for an order that is structurally unfillable on this chain.

Suggested fix
     function validateOrder(Ad memory ad, OrderParams calldata params) internal view returns (bytes32 orderHash) {
         if (!ad.open) revert AdManager__AdClosed();
         if (params.amount == 0) revert AdManager__ZeroAmount();
         if (params.bridger == bytes32(0)) revert AdManager__BridgerZero();
         if (params.orderRecipient == bytes32(0)) revert AdManager__RecipientZero();
+        toAddressChecked(params.orderRecipient);

         // Source chain must be supported and portal must match (if configured).
         ChainInfo memory ci = chains[params.orderChainId];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/evm/src/AdManager.sol` around lines 1154 - 1188, validateOrder
currently only checks params.orderRecipient != bytes32(0) but unlock() uses
toAddressChecked() assuming an on-chain EVM address; add a stricter validation
in validateOrder to reject non-EVM recipients by ensuring params.orderRecipient
encodes an EVM address (upper 12 bytes zero in the bytes32 representation) and
revert if not (introduce a clear error like AdManager__RecipientNotEVM). Update
validateOrder (referencing validateOrder, unlock, lockForOrder, and
toAddressChecked) to perform this check so you never lock liquidity for
structurally unfillable orders.
🧹 Nitpick comments (2)
contracts/evm/test/OrderPortal.t.sol (1)

573-590: Avoid unchecked truncation when asserting recipient balances.

These casts bypass the new checked-local-address behavior by truncating bytes32 directly to 20 bytes. That makes the tests less strict than production and can hide regressions where a non-EVM adRecipient should fail instead of being silently truncated.

Suggested helper and replacement
+    function _toAddrChecked(bytes32 value) internal pure returns (address) {
+        require(uint256(value) >> 160 == 0, "non-EVM address");
+        return address(uint160(uint256(value)));
+    }
+
-        uint256 balRecipientBefore = orderToken.balanceOf(address(uint160(uint256(p.adRecipient))));
+        uint256 balRecipientBefore = orderToken.balanceOf(_toAddrChecked(p.adRecipient));
...
-        assertEq(orderToken.balanceOf(address(uint160(uint256(p.adRecipient)))), balRecipientBefore, "recipient balance changed");
+        assertEq(orderToken.balanceOf(_toAddrChecked(p.adRecipient)), balRecipientBefore, "recipient balance changed");
...
-        uint256 balRecipientBefore = orderToken.balanceOf(address(uint160(uint256(p.adRecipient))));
+        uint256 balRecipientBefore = orderToken.balanceOf(_toAddrChecked(p.adRecipient));
...
-        assertEq(orderToken.balanceOf(address(uint160(uint256(p.adRecipient)))), balRecipientBefore + p.amount, "recipient not credited");
+        assertEq(orderToken.balanceOf(_toAddrChecked(p.adRecipient)), balRecipientBefore + p.amount, "recipient not credited");
...
-        uint256 balRecipientBefore = address(uint160(uint256(p.adRecipient))).balance;
+        uint256 balRecipientBefore = _toAddrChecked(p.adRecipient).balance;
...
-        assertEq(address(uint160(uint256(p.adRecipient))).balance, balRecipientBefore + p.amount, "recipient not credited");
+        assertEq(_toAddrChecked(p.adRecipient).balance, balRecipientBefore + p.amount, "recipient not credited");

Also applies to: 602-623, 652-672

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/evm/test/OrderPortal.t.sol` around lines 573 - 590, The test is
silently truncating p.adRecipient via address(uint160(uint256(...))) which
bypasses checked-local-address rules; replace those casts with a small helper
like toCheckedAddress(bytes32 x) used in the tests that verifies the upper 12
bytes are zero (e.g. require(uint96(uint256(x >> 160)) == 0) or check
(bytes12(x) == 0)) and then returns address(uint160(uint256(x))); update all
occurrences (e.g. the balanceOf arguments and any address(...) uses in
unlock-related asserts) to call toCheckedAddress(p.adRecipient) so the test
fails if a non-EVM recipient is provided instead of silently truncating.
contracts/evm/test/Admanager.t.sol (1)

1138-1138: Use a checked bytes32address helper in these assertions.

address(uint160(uint256(...))) silently truncates the upper 96 bits, so this test would still pass for a non-EVM orderRecipient even though production now rejects that case via toAddressChecked. Mirror the checked conversion here, or assert the upper 96 bits are zero before casting, so the suite validates the same invariant as unlock.

Suggested test helper
+    function _toAddrChecked(bytes32 value) internal pure returns (address) {
+        require(uint256(value) >> 160 == 0, "non-EVM address");
+        return address(uint160(uint256(value)));
+    }
+
-        uint256 balBefore = adToken.balanceOf(address(uint160(uint256(p.orderRecipient))));
+        uint256 balBefore = adToken.balanceOf(_toAddrChecked(p.orderRecipient));
...
-        uint256 balAfter = adToken.balanceOf(address(uint160(uint256(p.orderRecipient))));
+        uint256 balAfter = adToken.balanceOf(_toAddrChecked(p.orderRecipient));

Also applies to: 1161-1161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/evm/test/Admanager.t.sol` at line 1138, Replace the unchecked cast
address(uint160(uint256(p.orderRecipient))) used when reading balances with the
same checked conversion used in production: either call the test helper
toAddressChecked(p.orderRecipient) (or implement one that reverts/asserts if
uint256(p.orderRecipient) >> 160 != 0) and use that result for
adToken.balanceOf, or explicitly assert the upper 96 bits are zero before the
cast (e.g., require(uint256(p.orderRecipient) >> 160 == 0)) so that
p.orderRecipient, balBefore, and the adToken.balanceOf check mirror the
production toAddressChecked invariant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/cross-chain-e2e.yml:
- Around line 1-71: The workflow file "Cross-Chain E2E" is entirely commented
out; either delete the file, re-enable the workflow by uncommenting the job
block that defines the cross-chain-e2e job (including steps like
actions/checkout, foundry setup, and the Run cross-chain E2E test step that runs
scripts/run_cross_chain_e2e.sh), or replace the content with a minimal
placeholder job that succeeds (e.g., a job named cross-chain-e2e that runs a
simple echo or true step) so actionlint no longer flags an empty workflow.

In `@contracts/evm/src/OrderPortal.sol`:
- Around line 591-607: validateOrder currently allows any non-zero bytes32
adRecipient even though unlock() later calls
toAddressChecked(params.adRecipient) and a non-EVM address would be unpayable;
call toAddressChecked(params.adRecipient) inside validateOrder (use the existing
helper toAddressChecked) and require the resulting address != address(0) (or
revert with a new/appropriate error) so adRecipient is a valid 20-byte EVM
address before proceeding; update any error messages to reference adRecipient
and keep the rest of validateOrder logic unchanged.

In `@scripts/cross-chain-e2e/lib/proof.ts`:
- Around line 51-55: The ORDER_TYPEHASH constant is still built from the old
address-based type string, so update the type string passed to keccak256 in
ORDER_TYPEHASH to the new bytes32-based schema (replace each address and string
fields with bytes32) — e.g., "Order(bytes32 orderChainToken,bytes32
adChainToken,uint256 amount,bytes32 bridger,uint256 orderChainId,bytes32
orderPortal,bytes32 orderRecipient,uint256 adChainId,bytes32 adManager,bytes32
adId,bytes32 adCreator,bytes32 adRecipient,uint256 salt)"; update the
Buffer.from(...) argument that builds ORDER_TYPEHASH and ensure
computeOrderHash() and any signature/proof code still reference the same
ORDER_TYPEHASH symbol so all off-chain hashes match the on-chain bytes32 schema.

In `@scripts/cross-chain-e2e/run.ts`:
- Around line 325-339: The evmOrderParams object is using 20-byte address
strings for fields that the Solidity OrderParams now expects as bytes32; convert
each address-shaped value (orderChainToken, adChainToken, bridger,
orderRecipient, adManager, adCreator, adRecipient) to 32-byte values before
assigning to evmOrderParams (e.g., use the existing toBytes32 helper or
ethers.utils.hexZeroPad/hexZeroFill to left-pad each hex address to 32 bytes) so
the ABI/hash/signature matches the contract; keep SALT and other non-address
fields unchanged and ensure the converted bytes32 values are what you pass into
evmOrderParams.
- Around line 235-255: The Stellar IDs are being truncated to 20 bytes before
calling OrderPortal setters which expect exact bytes32 values; stop slicing and
pass full 32-byte IDs (or convert via the correct helper) to
evm.orderPortal.getFunction("setChain") and getFunction("setTokenRoute"):
replace the manual 20-byte truncation of stellarAdTokenHex (and any similar
slicing) with a conversion that returns a full bytes32 hex (e.g., use
stellarIdToEvmAddress or ensure the hex is zero‑padded to 32 bytes) so
STELLAR_CHAIN_ID, stellarAdManagerEvm, and the Stellar token ID are stored and
encoded as full bytes32 values.

In `@scripts/run_cross_chain_e2e.sh`:
- Line 89: The anvil process is currently bound to all interfaces using "--host
0.0.0.0", which exposes the dev RPC; update the anvil invocation in the
run_cross_chain_e2e.sh script (the line that starts the anvil background
process) to bind only to loopback (e.g., replace "--host 0.0.0.0" with "--host
127.0.0.1" or remove the host flag if default is loopback) so the local E2E
chain is not reachable from other hosts.

---

Outside diff comments:
In `@contracts/evm/src/AdManager.sol`:
- Around line 1154-1188: validateOrder currently only checks
params.orderRecipient != bytes32(0) but unlock() uses toAddressChecked()
assuming an on-chain EVM address; add a stricter validation in validateOrder to
reject non-EVM recipients by ensuring params.orderRecipient encodes an EVM
address (upper 12 bytes zero in the bytes32 representation) and revert if not
(introduce a clear error like AdManager__RecipientNotEVM). Update validateOrder
(referencing validateOrder, unlock, lockForOrder, and toAddressChecked) to
perform this check so you never lock liquidity for structurally unfillable
orders.

In `@contracts/stellar/contracts/ad-manager/src/lib.rs`:
- Around line 91-109: The function signature for set_manager (pub fn
set_manager(env: Env, manager: Address, status: bool) -> Result<(),
AdManagerError>) is not formatted per rustfmt; run cargo fmt --all (or apply
rustfmt) to reformat the declaration and body so CI passes, ensuring the
signature and parameter alignment for set_manager, and keep surrounding symbols
(Env, Address, AdManagerError, storage::set_manager, events::ManagerUpdated)
intact.
- Around line 193-216: E2E script calls still pass legacy auth flags to the
admin functions; update the script invocations that call set_chain and
set_token_route to remove the obsolete CLI flags (--signature, --public_key,
--auth_token, --time_to_expire) and invoke the new signatures instead: call
set_chain(order_chain_id, order_portal, supported) and call
set_token_route(ad_token, order_token, order_chain_id); ensure no manual
signature/auth_token handling remains and rely on the contract's require_auth()
enforcement (referenced in the contract functions requiring admin auth).

---

Nitpick comments:
In `@contracts/evm/test/Admanager.t.sol`:
- Line 1138: Replace the unchecked cast
address(uint160(uint256(p.orderRecipient))) used when reading balances with the
same checked conversion used in production: either call the test helper
toAddressChecked(p.orderRecipient) (or implement one that reverts/asserts if
uint256(p.orderRecipient) >> 160 != 0) and use that result for
adToken.balanceOf, or explicitly assert the upper 96 bits are zero before the
cast (e.g., require(uint256(p.orderRecipient) >> 160 == 0)) so that
p.orderRecipient, balBefore, and the adToken.balanceOf check mirror the
production toAddressChecked invariant.

In `@contracts/evm/test/OrderPortal.t.sol`:
- Around line 573-590: The test is silently truncating p.adRecipient via
address(uint160(uint256(...))) which bypasses checked-local-address rules;
replace those casts with a small helper like toCheckedAddress(bytes32 x) used in
the tests that verifies the upper 12 bytes are zero (e.g.
require(uint96(uint256(x >> 160)) == 0) or check (bytes12(x) == 0)) and then
returns address(uint160(uint256(x))); update all occurrences (e.g. the balanceOf
arguments and any address(...) uses in unlock-related asserts) to call
toCheckedAddress(p.adRecipient) so the test fails if a non-EVM recipient is
provided instead of silently truncating.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 928b9800-2848-4901-85de-3bbc7850db8b

📥 Commits

Reviewing files that changed from the base of the PR and between 5eacd33 and 397546e.

📒 Files selected for processing (28)
  • .github/workflows/cross-chain-e2e.yml
  • contracts/evm/js-scripts/deploy/core/setup.ts
  • contracts/evm/js-scripts/deploy/core/utils.ts
  • contracts/evm/js-scripts/utils.ts
  • contracts/evm/src/AdManager.sol
  • contracts/evm/src/OrderPortal.sol
  • contracts/evm/test/Admanager.t.sol
  • contracts/evm/test/OrderPortal.t.sol
  • contracts/evm/test/ProofBridge.t.sol
  • contracts/stellar/contracts/ad-manager/src/lib.rs
  • contracts/stellar/lib/proofbridge-core/src/eip712.rs
  • contracts/stellar/lib/proofbridge-core/src/test.rs
  • contracts/stellar/test_snapshots/test_ad_manager_lock_for_order.1.json
  • contracts/stellar/test_snapshots/test_ad_manager_unlock_with_bridger_proof.1.json
  • contracts/stellar/test_snapshots/test_full_cross_chain_flow.1.json
  • contracts/stellar/test_snapshots/test_nullifier_prevents_double_unlock.1.json
  • contracts/stellar/test_snapshots/test_order_portal_create_and_unlock.1.json
  • contracts/stellar/tests/fixtures/generate_fixtures.ts
  • contracts/stellar/tests/integration_test.rs
  • pnpm-workspace.yaml
  • scripts/cross-chain-e2e/lib/evm.ts
  • scripts/cross-chain-e2e/lib/proof.ts
  • scripts/cross-chain-e2e/lib/signing.ts
  • scripts/cross-chain-e2e/lib/stellar.ts
  • scripts/cross-chain-e2e/package.json
  • scripts/cross-chain-e2e/run.ts
  • scripts/cross-chain-e2e/tsconfig.json
  • scripts/run_cross_chain_e2e.sh

Comment thread .github/workflows/cross-chain-e2e.yml
Comment thread contracts/evm/src/OrderPortal.sol
Comment thread scripts/cross-chain-e2e/lib/proof.ts
Comment thread scripts/cross-chain-e2e/run.ts
Comment thread scripts/cross-chain-e2e/run.ts
Comment thread scripts/run_cross_chain_e2e.sh
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
contracts/evm/test/ProofBridge.t.sol (1)

97-99: Add at least one non-address bytes32 fixture to the parity path.

These helpers still derive every cross-chain identifier from _b32(address), so the upper 12 bytes are always zero in the updated hash-parity tests. That means a regression that accidentally truncates bytes32 values back to 20 bytes would still pass here, even though the PR’s main goal is lossless 32-byte Stellar round-tripping.

Also applies to: 204-213, 222-231

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/evm/test/ProofBridge.t.sol` around lines 97 - 99, Tests currently
derive every bytes32 via _b32(address) which zeros the top 12 bytes, so add at
least one non-address bytes32 fixture (e.g., a bytes32 literal with non-zero
high 12 bytes) into the parity test vectors used in the parity path and
hash-parity tests to ensure 32-byte values aren't being truncated; update the
arrays/fixtures referenced alongside _b32 (including the blocks around the
existing _b32 helper and the test vector groups around the ranges noted) to
include that non-address bytes32 value and ensure the parity assertions exercise
it.
contracts/evm/test/Admanager.t.sol (1)

1049-1054: Please cover the checked local-recipient decode path as well.

_openOrder always feeds orderRecipient as _b32(address), and the balance assertions then truncate it back with address(uint160(uint256(...))). That keeps these tests green for EVM-shaped values, but it does not protect the new toAddressChecked(bytes32) behavior on unlock. A negative case with a high-bit-set bytes32 recipient would close that gap.

Also applies to: 1144-1168

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/evm/test/Admanager.t.sol` around lines 1049 - 1054, Add a test
exercising the checked local-recipient decode path by calling _openOrder with an
orderRecipient that is a bytes32 value with high bits set (not a plain
_b32(address)) so that unlock will invoke toAddressChecked(bytes32) and
fail/revert as expected; modify the existing test that uses
_defaultParams/adId/_b32(_recipient) to instead create a maliciousRecipient =
bytes32(uint256(1) << 255 | uint256(uint160(validRecipient))) (or another
high-bit-set value) and set p.orderRecipient = maliciousRecipient, then assert
the unlock (or the balance assertions around unlock) reverts or behaves per the
new toAddressChecked semantics; reference functions/vars: _openOrder,
_defaultParams, p.orderRecipient, _b32, _openOrder, unlock, toAddressChecked to
locate where to inject this negative-case check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@contracts/evm/test/Admanager.t.sol`:
- Around line 1049-1054: Add a test exercising the checked local-recipient
decode path by calling _openOrder with an orderRecipient that is a bytes32 value
with high bits set (not a plain _b32(address)) so that unlock will invoke
toAddressChecked(bytes32) and fail/revert as expected; modify the existing test
that uses _defaultParams/adId/_b32(_recipient) to instead create a
maliciousRecipient = bytes32(uint256(1) << 255 |
uint256(uint160(validRecipient))) (or another high-bit-set value) and set
p.orderRecipient = maliciousRecipient, then assert the unlock (or the balance
assertions around unlock) reverts or behaves per the new toAddressChecked
semantics; reference functions/vars: _openOrder, _defaultParams,
p.orderRecipient, _b32, _openOrder, unlock, toAddressChecked to locate where to
inject this negative-case check.

In `@contracts/evm/test/ProofBridge.t.sol`:
- Around line 97-99: Tests currently derive every bytes32 via _b32(address)
which zeros the top 12 bytes, so add at least one non-address bytes32 fixture
(e.g., a bytes32 literal with non-zero high 12 bytes) into the parity test
vectors used in the parity path and hash-parity tests to ensure 32-byte values
aren't being truncated; update the arrays/fixtures referenced alongside _b32
(including the blocks around the existing _b32 helper and the test vector groups
around the ranges noted) to include that non-address bytes32 value and ensure
the parity assertions exercise it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 78e91fbd-e988-4500-a9f7-8583267dae12

📥 Commits

Reviewing files that changed from the base of the PR and between 397546e and 5ef469f.

📒 Files selected for processing (4)
  • contracts/evm/test/Admanager.t.sol
  • contracts/evm/test/OrderPortal.t.sol
  • contracts/evm/test/ProofBridge.t.sol
  • contracts/stellar/contracts/ad-manager/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/evm/test/OrderPortal.t.sol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant